package com.xiaoyi.sm;

import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.util.encoders.Hex;

import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.spec.ECGenParameterSpec;

/**
 * @author xiaoyi
 * @version 1.0.0
 * @date 2023/07/31 09:51:00
 */


public class SM2 {

    public SM2(){ }
    static {  //加载BC驱动
        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null)
            new BouncyCastleProvider();
    }

    // 生成sm2密钥对
    public KeyPair createECKeyPair() {
        //使用标准名称创建EC参数生成的参数规范
        final ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");

        // 获取一个椭圆曲线类型的密钥对生成器
        final KeyPairGenerator kpg;
        try {
            kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
// 使用SM2算法域参数集初始化密钥生成器（默认使用最高优先级安装的提供者的 SecureRandom 的实现作为随机源）
            // kpg.initialize(sm2Spec);
            // 使用SM2的算法域参数集和指定的随机源初始化密钥生成器
            kpg.initialize(sm2Spec, new SecureRandom());
            // 通过密钥生成器生成密钥对
            return kpg.generateKeyPair();

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /*
     *公钥加密
     * @param publicKey SM2公钥
     * @param data      明文数据
     * @param modeType  加密模式
     * @return          密文数据
     */
    public byte[] encode(String publicKeyHex, byte[] inputBytes, int modeType){
        //加密模式
        //
        BCECPublicKey publicKey = getECPublicKeyByPublicKeyHex(publicKeyHex);
        SM2Engine.Mode mode;
        if (modeType == 1) {//采用新模式加密标准
            mode = SM2Engine.Mode.C1C3C2;
        } else {//采用旧模式加密标准
            mode = SM2Engine.Mode.C1C2C3;
        }
        //通过公钥对象获取公钥的基本域参数。
        ECParameterSpec ecParameterSpec = publicKey.getParameters();
        ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(),
                ecParameterSpec.getG(), ecParameterSpec.getN());
        //通过公钥值和公钥基本参数创建公钥参数对象
        ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(publicKey.getQ(), ecDomainParameters);
        //根据加密模式实例化SM2公钥加密引擎
        SM2Engine sm2Engine = new SM2Engine(mode);
        //初始化加密引擎
        sm2Engine.init(true, new ParametersWithRandom(ecPublicKeyParameters, new SecureRandom()));

        byte[] arrayOfBytes = null;
        try {
            //通过加密引擎对字节数串行加密
            arrayOfBytes = sm2Engine.processBlock(inputBytes, 0, inputBytes.length);
        } catch (Exception e) {
            System.out.println("SM2加密时出现异常:" + e.getMessage());
            e.printStackTrace();
        }
        return arrayOfBytes;
    }
    /**
     * 私钥解密
     *
     * @param privateKeyHex  SM私钥
     * @param cipherBytes 密文数据
     * @param modeType 加密模式
     * @return           解密后的明文
     */
    public static byte[] decode(String privateKeyHex, byte[] cipherBytes, int modeType) {
        //解密模式
        SM2Engine.Mode mode;
        BCECPrivateKey privateKey = getBCECPrivateKeyByPrivateKeyHex(privateKeyHex);
        if (modeType == 1) {
            mode = SM2Engine.Mode.C1C3C2;
        } else {
            mode = SM2Engine.Mode.C1C2C3;
        }

        //通过私钥对象获取私钥的基本域参数。
        ECParameterSpec ecParameterSpec = privateKey.getParameters();
        ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(),
                ecParameterSpec.getG(), ecParameterSpec.getN());

        //通过私钥值和私钥基本参数创建私钥参数对象
        ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(privateKey.getD(),
                ecDomainParameters);

        //通过解密模式创建解密引擎并初始化
        SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
        sm2Engine.init(false, ecPrivateKeyParameters);

        byte[] arrayOfBytes = null;
        try {
            //通过解密引擎对密文字节串进行解密
            arrayOfBytes = sm2Engine.processBlock(cipherBytes, 0, cipherBytes.length);
        } catch (Exception e) {
            System.out.println("SM2解密时出现异常" + e.getMessage());
        }
        return arrayOfBytes;
    }


    //椭圆曲线ECParameters ASN.1 结构
    private static X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
    //椭圆曲线公钥或私钥的基本域参数。
    private static ECParameterSpec ecDomainParameters = new ECParameterSpec(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN());

    /**
     * 公钥字符串转换为 BCECPublicKey 公钥对象
     *
     * @param pubKeyHex 64字节十六进制公钥字符串(如果公钥字符串为65字节首字节为0x04：表示该公钥为非压缩格式，操作时需要删除)
     * @return BCECPublicKey SM2公钥对象
     */

    public static BCECPublicKey getECPublicKeyByPublicKeyHex(String pubKeyHex) {
        //截取64字节有效的SM2公钥（如果公钥首字节为0x04）
        if (pubKeyHex.length() > 128) {
            pubKeyHex = pubKeyHex.substring(pubKeyHex.length() - 128);
        }
        //将公钥拆分为x,y分量（各32字节）
        String stringX = pubKeyHex.substring(0, 64);
        String stringY = pubKeyHex.substring(stringX.length());
        //将公钥x、y分量转换为BigInteger类型
        BigInteger x = new BigInteger(stringX, 16);
        BigInteger y = new BigInteger(stringY, 16);
        //通过公钥x、y分量创建椭圆曲线公钥规范
        ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(x9ECParameters.getCurve().createPoint(x, y), ecDomainParameters);
        //通过椭圆曲线公钥规范，创建出椭圆曲线公钥对象（可用于SM2加密及验签）
        return new BCECPublicKey("EC", ecPublicKeySpec, BouncyCastleProvider.CONFIGURATION);
    }

    /**
     * 私钥字符串转换为 BCECPrivateKey 私钥对象
     *
     * @param privateKeyHex: 32字节十六进制私钥字符串
     * @return BCECPrivateKey:SM2私钥对象
     */
    public static BCECPrivateKey getBCECPrivateKeyByPrivateKeyHex(String privateKeyHex) {
        //将十六进制私钥字符串转换为BigInteger对象
        BigInteger d = new BigInteger(privateKeyHex, 16);
        //通过私钥和私钥域参数集创建椭圆曲线私钥规范
        ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(d, ecDomainParameters);
        //通过椭圆曲线私钥规范，创建出椭圆曲线私钥对象（可用于SM2解密和签名）
        return new BCECPrivateKey("EC", ecPrivateKeySpec, BouncyCastleProvider.CONFIGURATION);
    }

    public static void main(String[] args) throws Exception {
        String Sm4Key="0123456789abcdef";
        String sm2PublicKeyHex=null;
        String sm2PrivateKeyHex=null;

        SM2 sm2=new SM2();
        // 生成SM2密钥
        KeyPair keyPair = sm2.createECKeyPair();
        PrivateKey privateKey = keyPair.getPrivate();
        PublicKey publicKey = keyPair.getPublic();
        if (publicKey instanceof BCECPublicKey){
            //获取65字节非压缩的十六进制公钥串(0x04)
            byte[] publicKeyBytes = ((BCECPublicKey) publicKey).getQ().getEncoded(false);
            sm2PublicKeyHex = Hex.toHexString(publicKeyBytes);

        }
        if (privateKey instanceof BCECPrivateKey) {
            //获取32字节十六进制私钥串
            sm2PrivateKeyHex = ((BCECPrivateKey) privateKey).getD().toString(16);

        }
        //Sm2加密密钥
        byte [] keyBytes=Sm4Key.getBytes("utf-8");
        byte[] encodeBytes = sm2.encode(sm2PublicKeyHex, keyBytes, 1);
        String encodeKey = Hex.toHexString(encodeBytes);
        //Sm2解密密钥
        byte [] decodeBytes=sm2.decode(sm2PrivateKeyHex,encodeBytes,1);
        String deKey=new String(decodeBytes,"utf-8");
        System.out.println("\ninitial key:"+Sm4Key+"\nencode key:"+encodeKey+"\ndecode key:"+deKey);
        System.out.println("complete!");
    }
}

